/**
 * Copyright (c) 2013,  Paul Daniels
 *
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 *
 * Copyright (C) 2010-2013 eBusiness Information, Excilys Group
 *
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 *
 * Unless required by applicable law or agreed To in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 *
 *
 * Copyright (C) 2010-2013 eBusiness Information, Excilys Group
 *
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 *
 * Unless required by applicable law or agreed To in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 *
 *
 * Copyright (C) 2010-2013 eBusiness Information, Excilys Group
 *
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 *
 * Unless required by applicable law or agreed To in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
/**
 * Copyright (C) 2010-2013 eBusiness Information, Excilys Group
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed To in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.hupu.jrs.components.jockeyjs.util

import android.util.Log
import java.util.*
import java.util.concurrent.*
import java.util.concurrent.atomic.AtomicBoolean

object BackgroundExecutor {

    private val TAG = "BackgroundExecutor"

    private var executor: Executor = Executors.newScheduledThreadPool(2 * Runtime.getRuntime().availableProcessors())

    private val tasks = ArrayList<Task>()

    /**
     * Execute a runnable after the given delay.
     *
     * @param runnable the task to execute
     * @param delay    the time from now to delay execution, in milliseconds
     * @return Future associated to the running task
     * @throws IllegalArgumentException if `delay` is strictly positive and the current
     * executor does not support scheduling (if
     * [.setExecutor] has been called with such an
     * executor)
     */
    private fun directExecute(runnable: Runnable, delay: Int): Future<*>? {
        var future: Future<*>? = null
        if (delay > 0) {
            /* no serial, but a delay: schedule the task */
            if (executor !is ScheduledExecutorService) {
                throw IllegalArgumentException("The executor set does not support scheduling")
            }
            val scheduledExecutorService = executor as ScheduledExecutorService
            future = scheduledExecutorService.schedule(runnable, delay.toLong(), TimeUnit.MILLISECONDS)
        } else {
            if (executor is ExecutorService) {
                val executorService = executor as ExecutorService
                future = executorService.submit(runnable)
            } else {
                /* non-cancellable task */
                executor.execute(runnable)
            }
        }
        return future
    }

    /**
     * Execute a task after (at least) its delay **and** after all
     * tasks added with the same non-null `serial` (if any) have
     * completed execution.
     *
     * @param task the task to execute
     * @throws IllegalArgumentException if `task.delay` is strictly positive and the
     * current executor does not support scheduling (if
     * [.setExecutor] has been called with such an
     * executor)
     */
    @Synchronized
    fun execute(task: Task) {
        var future: Future<*>? = null
        if (task.serial == null || !hasSerialRunning(task.serial)) {
            task.executionAsked = true
            future = directExecute(task, task.remainingDelay)
        }
        if (task.id != null || task.serial != null) {
            /* keep task */
            task.future = future
            tasks.add(task)
        }
    }

    /**
     * Execute a task.
     *
     * @param runnable the task to execute
     * @param id       identifier used for task cancellation
     * @param delay    the time from now to delay execution, in milliseconds
     * @param serial   the serial queue (`null` or `""` for no
     * serial execution)
     * @throws IllegalArgumentException if `delay` is strictly positive and the current
     * executor does not support scheduling (if
     * [.setExecutor] has been called with such an
     * executor)
     */
    fun execute(runnable: Runnable, id: String, delay: Int, serial: String) {
        execute(object : Task(id, delay, serial) {
            override fun execute() {
                runnable.run()
            }
        })
    }

    /**
     * Execute a task after the given delay.
     *
     * @param runnable the task to execute
     * @param delay    the time from now to delay execution, in milliseconds
     * @throws IllegalArgumentException if `delay` is strictly positive and the current
     * executor does not support scheduling (if
     * [.setExecutor] has been called with such an
     * executor)
     */
    fun execute(runnable: Runnable, delay: Int) {
        directExecute(runnable, delay)
    }

    /**
     * Execute a task.
     *
     * @param runnable the task to execute
     */
    fun execute(runnable: Runnable) {
        directExecute(runnable, 0)
    }

    /**
     * Execute a task after all tasks added with the same non-null
     * `serial` (if any) have completed execution.
     *
     *
     * Equivalent to [ execute(runnable, id, 0, serial)][.execute].
     *
     * @param runnable the task to execute
     * @param id       identifier used for task cancellation
     * @param serial   the serial queue to use (`null` or `""`
     * for no serial execution)
     */
    fun execute(runnable: Runnable, id: String, serial: String) {
        execute(runnable, id, 0, serial)
    }

    /**
     * Change the executor.
     *
     * Note that if the given executor is not a [ScheduledExecutorService]
     * then executing a task after a delay will not be supported anymore. If it
     * is not even a [ExecutorService] then tasks will not be cancellable
     * anymore.
     *
     * @param executor
     * the new executor
     */
    fun setExecutor(executor: Executor) {
        BackgroundExecutor.executor = executor
    }

    /**
     * Cancel all tasks having the specified `id`.
     *
     * @param id
     * the cancellation identifier
     * @param mayInterruptIfRunning
     * `true` if the thread executing this task should be
     * interrupted; otherwise, in-progress tasks are allowed to
     * complete
     */
    @Synchronized
    fun cancelAll(id: String, mayInterruptIfRunning: Boolean) {
        for (i in tasks.indices.reversed()) {
            val task = tasks[i]
            if (id == task.id) {
                if (task.future != null) {
                    task.future!!.cancel(mayInterruptIfRunning)
                    if (!task.managed.getAndSet(true)) {
                        /*
						 * the task has been submitted to the executor, but its
						 * execution has not started yet, so that its run()
						 * method will never call postExecute()
						 */
                        task.postExecute()
                    }
                } else if (task.executionAsked) {
                    Log.w(TAG, "A task with id "
                            + task.id
                            + " cannot be cancelled (the executor set does not support it)")
                } else {
                    /* this task has not been submitted to the executor */
                    tasks.removeAt(i)
                }
            }
        }
    }

    /**
     * Indicates whether a task with the specified `serial` has been
     * submitted to the executor.
     *
     * @param serial
     * the serial queue
     * @return `true` if such a task has been submitted,
     * `false` otherwise
     */
    private fun hasSerialRunning(serial: String?): Boolean {
        for (task in tasks) {
            if (task.executionAsked && serial == task.serial) {
                return true
            }
        }
        return false
    }

    /**
     * Retrieve and remove the first task having the specified
     * `serial` (if any).
     *
     * @param serial
     * the serial queue
     * @return task if found, `null` otherwise
     */
    private fun take(serial: String): Task? {
        val len = tasks.size
        for (i in 0 until len) {
            if (serial == tasks[i].serial) {
                return tasks.removeAt(i)
            }
        }
        return null
    }

    abstract class Task(id: String, delay: Int, serial: String) : Runnable {

        var id: String? = null
        var remainingDelay: Int = 0
        private var targetTimeMillis: Long = 0 /* since epoch */
        var serial: String? = null
        var executionAsked: Boolean = false
        var future: Future<*>? = null

        /*
         * A task can be cancelled after it has been submitted to the executor
         * but before its run() method is called. In that case, run() will never
         * be called, hence neither will postExecute(): the tasks with the same
         * serial identifier (if any) will never be submitted.
         *
         * Therefore, cancelAll() *must* call postExecute() if run() is not
         * started.
         *
         * This flag guarantees that either cancelAll() or run() manages this
         * task post execution, but not both.
         */
        val managed = AtomicBoolean()

        init {
            if ("" != id) {
                this.id = id
            }
            if (delay > 0) {
                remainingDelay = delay
                targetTimeMillis = System.currentTimeMillis() + delay
            }
            if ("" != serial) {
                this.serial = serial
            }
        }

        override fun run() {
            if (managed.getAndSet(true)) {
                /* cancelled and postExecute() already called */
                return
            }

            try {
                execute()
            } finally {
                /* handle next tasks */
                postExecute()
            }
        }

        abstract fun execute()

        fun postExecute() {
            if (id == null && serial == null) {
                /* nothing to do */
                return
            }
            synchronized(BackgroundExecutor::class.java) {
                /* execution complete */
                tasks.remove(this)

                if (serial != null) {
                    val next = take(serial!!)
                    if (next != null) {
                        if (next.remainingDelay != 0) {
                            /* the delay may not have elapsed yet */
                            next.remainingDelay = Math.max(0, (targetTimeMillis - System.currentTimeMillis()).toInt())
                        }
                        /* a task having the same serial was queued, execute it */
                        BackgroundExecutor.execute(next)
                    }
                }
            }
        }
    }
}
